feat: Add remaining getting-started snippets.#398
Draft
kinyoklion wants to merge 20 commits intorlamb/sdk-snippetsfrom
Draft
feat: Add remaining getting-started snippets.#398kinyoklion wants to merge 20 commits intorlamb/sdk-snippetsfrom
kinyoklion wants to merge 20 commits intorlamb/sdk-snippetsfrom
Conversation
b24d0d0 to
4ea69f2
Compare
keelerm84
approved these changes
Apr 28, 2026
Adds snippet content for every SDK in gonfalon/static/ld/components/
getStarted/sdk/ outside of python-server-sdk (which landed in the
prior slice). Each SDK has a `sdk.yaml` descriptor and one
`<name>.snippet.md` per gonfalon `<Snippet>` block.
Tier 1 — server-side / Linux Docker (validators in this PR):
go-server-sdk, node-server-sdk, node-client-sdk, ruby-server-sdk,
php-server-sdk, rust-server-sdk, dotnet-server-sdk,
dotnet-client-sdk, java-server-sdk
Tier 1 — server-side, validator pending:
haskell-server-sdk, erlang-server-sdk, lua-server-sdk,
cpp-server-sdk, cpp-client-sdk
Tier 2 — browser:
js-client-sdk (Playwright validator in this PR), vue-client-sdk
(validator pending), react-client-sdk (legacy + createApp variants;
legacy renders into gonfalon, createApp deferred until its
assetSource pattern is migrated)
Tier 3 — mobile/native (validators all pending):
android-client-sdk, flutter-client-sdk, react-native-client-sdk
Tier 4 — iOS:
ios-client-sdk (validator pending — needs macos-* runner)
Skipped:
roku-client-sdk (validation: none; manual procedure documented in
frontmatter)
Each SDK with markers in gonfalon was verified: `snippets render`
idempotent, `snippets verify` ok. The 11 SDKs with validators in
this PR were exercised end-to-end against the real LD test
environment locally. Three "fix on red" surfaces baked in:
- node-client-sdk + dotnet-client-sdk: rewrote the print line so
the snippet emits the EXAM-HELLO canonical phrase
`feature flag evaluates to true` instead of `Feature flag X is
true`. Snippets matched the gonfalon source verbatim before
the fix; validators surfaced the divergence.
- java-server-sdk: gonfalon's stale `5.0.0` version fallback
swapped for `${version}` only; the validator's synthesized pom
pins 7.13.4 (current Maven Central).
Snippets that contain non-EXAM-HELLO output today (e.g. cpp's
`Feature flag '<key>' is true`) carry their content verbatim with a
`# Validator pending` comment in frontmatter; the snippet is
rendered into gonfalon today and the canonical-line fix follows
when the per-SDK validator lands.
Each `validators/languages/<runtime>/` carries a `runner.yaml`
declaring `mode: docker`, an `image-prefix:` for the per-validator
content-hash tag, and a `runs-on:` hint. The Dockerfile builds the
language image (with the shared lib at `/harness-shared` per the
build-context refactor in the parent branch) and the harness sources
`/harness-shared/lib.sh` for the polling/timeout loop and key-
redacting log dump.
Runtimes added:
- go → go-server-sdk
- node → node-server-sdk + node-client-sdk
- ruby → ruby-server-sdk (with $stdout.sync = true shim
so block-buffered output reaches the matcher)
- php → php-server-sdk (composer pre-installed)
- dotnet-server → dotnet-server-sdk (synthesizes a minimal .csproj)
- dotnet-client → dotnet-client-sdk (top-level-statements .csproj)
- jvm → java-server-sdk (synthesizes pom.xml pinning
LD java 7.13.4 + maven-assembly-plugin; harness
prepends `package com.launchdarkly.tutorial;`
to App.java since `mvn archetype:generate`
writes that line for users)
- rust → rust-server-sdk (cargo new + cargo add)
- browser → js-client-sdk (Playwright v1.59.1 + Chromium;
check.js loads file:///snippet/index.html in
headless Chrome and polls page text for the
success line)
All eleven validators (python from the prior slice + ten new ones)
were verified end-to-end against the real LD test environment except
rust (cargo cold-build is multi-minute; CI exercises it). Each
validator that surfaced a snippet bug got fixed via "fix on red" in
the corresponding snippet — see the parent commit's notes.
`.github/workflows/snippets-validate.yml` runs `snippets validate --sdk=<id>` for every validator-bearing SDK as a parallel matrix cell. fail-fast is disabled so every cell runs to completion. Each cell uses `launchdarkly/gh-actions/actions/verify-hello-app@ verify-hello-app-v2.0.1` — the same action the hello-* repos use — to assume the AWS role from `vars.AWS_ROLE_ARN` (a repo *variable*, not a secret) via OIDC, fetch the LaunchDarkly Sandbox account credential from Secrets Manager, and inject it as LAUNCHDARKLY_SDK_KEY / LAUNCHDARKLY_CLIENT_SIDE_ID / LAUNCHDARKLY_MOBILE_KEY based on each row's `key-type:` field (server | client | mobile). LAUNCHDARKLY_FLAG_KEY=sample-feature is exported in the `command:` block; the action only handles the SDK-side credential. No static LD secrets live on this repo. After the matrix finishes, a final `summary` job downloads each cell's status + log artifact, writes a markdown table to $GITHUB_STEP_SUMMARY listing each SDK / pass-or-fail / 3-line excerpt of the failure log, and exits non-zero if any cell failed so the PR check goes red. Initial matrix covers the eleven SDKs with validators landed in this PR (python from the parent branch + ten new). Additional rows land as more validators get wired (haskell, erlang, lua, cpp, ios, vue, android, flutter, react-native, react-client, dotnet-client once a real mobile key is provisioned). See validators/languages/ for the current list.
The repo variable was provisioned as AWS_ROLE_ARN_EXAMPLES (scoped to the examples sandbox) rather than the generic AWS_ROLE_ARN the hello-* repos use. Update both the role_arn input and the header comment.
The verify-hello-app action already pulls LAUNCHDARKLY_FLAG_KEY from the SSM parameter /sdk/common/hello-apps/boolean-flag-key — that's how every hello-* repo's CI gets the flag key without setting it in its own workflow. Our `export LAUNCHDARKLY_FLAG_KEY=sample-feature` in the command block was redundant and would silently override the shared value if it ever changes (e.g. if the test environment swaps the canonical flag key). Drop it; let the action manage it.
Three fixes from the first CI run on the validator matrix:
- rust validator: bump rust:1.83 → rust:1.85 base image. The
transitive dep `time-macros 0.2.27` requires the `edition2024`
cargo feature, only stabilized in 1.85.
- js-client-sdk: context key was `'example-context-key'`. Every other
hello-app uses `'example-user-key'`, and the test environment's
`hello-boolean` flag is configured to evaluate to true for that
user. The mismatched key was a snippet-author idiosyncrasy in
gonfalon, not a deliberate divergence — normalize.
- dotnet-client-sdk: same root cause — `Context.New("context-key-
123abc")` swapped for `Context.New("example-user-key")`. Validator
observed the flag evaluating to False against `context-key-123abc`
in the shared test env.
The other 8 cells (java, python, dotnet-server, php, node-client,
go, node-server, ruby) all green on this run.
LaunchDarkly already uses 'tier' to classify SDKs (the SDK tier list); re-using T1/T2/T3/T4 in our validator matrix to mean 'Linux Docker' vs 'browser' vs 'mobile' vs 'macOS' creates a name collision that will mislead anyone skimming the workflow. Group the matrix rows by plain runtime descriptor instead.
Pre-clones cpp-sdks at launchdarkly-cpp-server-v3.10.1 and prewarms ccache by building the SDK once at image-build time, so per-validate cycles only compile the user's main.cpp. Snippet's print line was the legacy "Feature flag '<key>' is true" pattern; updated to the EXAM-HELLO canonical form so await_success_line matches.
Same shape as the cpp-server validator: pre-clones cpp-sdks at launchdarkly-cpp-client-v3.11.1, prewarms ccache by building the client-SDK target once. Snippet's print line was the legacy "Feature flag '<key>' is true" pattern; updated to the EXAM-HELLO canonical.
The Lua SDK is a thin wrapper around the C++ Server SDK's C binding, so the validator image builds cpp-sdks (launchdarkly-cpp-server-v3.10.1) as a shared library at image-build time and luarocks-installs the wrapper against it. Per-validate is just `lua hello.lua` against the staged snippet. Snippet's print line was the legacy "Feature flag '<key>' is <bool>" pattern; updated to EXAM-HELLO canonical.
Fixes a long-standing bug in the snippet (carried over from gonfalon):
the program called lookupEnv "{{ featureKey }}", which renders as
lookupEnv "sample-feature" — looking up an env var literally named after
the flag, which always failed at runtime. The Nothing branch then
defaulted back to "sample-feature", so the program worked by coincidence.
Hello-haskell-server uses lookupEnv "LAUNCHDARKLY_FLAG_KEY"; aligning
the snippet with the same pattern.
Validator uses haskell:9.6.7-bullseye with a pre-compiled cabal project
pinning launchdarkly-server-sdk-4.5.1 (latest on Hackage). The snippet's
stack.yaml/package.yaml fragments are still authored for ld-application
rendering but aren't exercised by the validator — the equivalent cabal
project is baked into the image.
Validator builds on the playwright base image: pre-bakes a Vue 3 + Vite
project with launchdarkly-vue-client-sdk@2.5.0, vue@3.5.33, vite@8.0.10
installed and warmed. Per-validate stages the snippet's src/main.js and
src/App.vue, runs vite build, starts vite preview, and points headless
Chromium at it.
Snippet's canonical line was "Feature Flag {{ featureKey }} is
{{ flagValue }}", which doesn't match the EXAM-HELLO success regex.
Updated to "The {{ featureKey }} feature flag evaluates to
{{ flagValue }}." — Vue's runtime mustaches survive the snippet
template engine via foreign-template pass-through.
CI's hello-boolean flag targets contexts whose user key is example-user-key; the vue snippet's main.js used example-user, so the flag returned its default (false) in CI. All other validated client SDKs (node-client, dotnet-client, ios, android, react-native, js-client) already use example-user-key. Cleaning up the rendered comment marker on the snippet at the same time.
Engine: extend the template language with `{{ name | filter }}`. Today
only `camelCase` is supported. For ld-application this renders as
`${filter(name)}` (matching gonfalon's existing camelCase use); for
validation the filter is applied to the env-var value (`sample-feature`
becomes `sampleFeature`). Undeclared names pass through with the filter
preserved, so foreign templates aren't disturbed.
This unblocks react-client-sdk's snippets, where useFlags() destructures
camelCased identifiers from kebab-cased flag keys: the snippet body now
uses `{{ featureKey | camelCase }}`. Updated app-tsx and legacy-app-tsx
accordingly.
Validator: Vite + React + launchdarkly-react-client-sdk@3.9.0 image with
the bundle pre-warmed. Per-validate stages App.tsx + the entrypoint
(index.tsx for legacy, main.tsx for createApp), rewrites index.html to
point at it, builds, serves vite preview, and probes the DOM with
Playwright.
Wired the legacy variant: legacy-app-tsx now declares validation.runtime
+ companions, and gonfalon's legacy.tsx drops its hand-authored
"camelCase filter pending" Snippet in favor of the marker-driven render
(byte-equivalent JSX, just template-literal-formatted differently).
The createApp variant stays unwired — it needs gonfalon's createApp.tsx
to drop its assetSource/replaceAll pattern, plus the snippet's
<LDProvider> needs an explicit context= for the hello-app's user key.
Schema: extend ld-application with a `get-started-files: [list]` field alongside the existing `get-started-file: string`, so a single SDK can target multiple consumer files. react-client-sdk now renders into both gonfalon variants (legacy.tsx + createApp.tsx). Gonfalon: refactor createApp.tsx off its assetSource `?raw + replaceAll` pattern. The snippet system can't intercept that — replacing it with inline template literals so the markers do their normal job. The assetSource/main.tsx.txt and assetSource/App.tsx.txt files are no longer imported but left in place to keep this PR focused on the createApp.tsx behavior. Snippet: createApp's main-tsx was missing explicit context= on <LDProvider>, so the SDK evaluated against an anonymous user and hello-boolean returned its default. Added the standard example-user-key context, matching the legacy variant + every other client SDK. Validator: same react-client runtime as legacy. With both variants now declared validation.runtime, the CI cell exercises both in sequence within the react-client-sdk job.
Validator runs Flutter's web build target — the only Linux-native path for an SDK whose other targets need iOS/Android tooling. The Dockerfile extends the playwright base with the Flutter SDK at /opt/flutter, pre-creates a hello_flutter project, pins launchdarkly_flutter_client_sdk@4.16.0 + provider@6.1.2, and pre-warms `flutter build web` so per-validate cycles only recompile main.dart. Per-validate: stage lib/main.dart, run `flutter build web` with `--dart-define LAUNCHDARKLY_CLIENT_SIDE_ID=...` so the snippet's CredentialSource.fromEnvironment() picks the credential up at compile time, static-serve build/web with python's http.server, probe the DOM with Playwright. The harness clicks Flutter's <flt-semantics-placeholder> to force the semantics tree to populate text into the DOM where Playwright can read it. Snippet itself is a clean port — gonfalon's main.dart already prints the canonical EXAM-HELLO line via Dart string interpolation.
The Get Started flow is interactive (rebar3 shell + manual gen_server:call), so the snippet itself never prints the canonical EXAM-HELLO line. Synthesize the equivalent at validate time: pre-bake a hello_erlang OTP application with rebar.config + app.src + supervisor pinned to launchdarkly_server_sdk@3.9.0, drop in the snippet's hello_erlang_server.erl per-validate, run `rebar3 compile`, and drive `erl -noshell -eval` to ensure_all_started + sleep + hello_erlang_server:get/3 + io:format the canonical line + init:stop. `rebar3 eval` doesn't exist as a built-in task on the bundled image, so we drive `erl` directly with -pa pointing at the compiled beam dirs under _build/default/lib/*/ebin.
Skip the Android emulator entirely: Robolectric runs the activity
lifecycle in the JVM with stubbed Android framework + real
OkHttp/network, which is enough to exercise the snippet's
init+evaluate path against the live LaunchDarkly streaming API.
Dockerfile clones hello-android (LD's official Get Started reference)
for project scaffolding, patches app/build.gradle to pin
launchdarkly-android-client-sdk@5.11.1 + Robolectric 4.12.2, and adds
a HelloAppTest that:
1. Triggers MainApplication.onCreate via ApplicationProvider so
LDClient.init runs with the snippet-baked mobile key.
2. Drives MainActivity through Robolectric's lifecycle controller.
3. Polls textView.text for the canonical EXAM-HELLO line, flushing
the foreground looper between checks so the streaming SDK's flag
listener can fire.
Per-validate is just `gradlew testDebugUnitTest`. Total local time
(warm gradle cache): ~45s.
Skip Metro + iOS simulator + Android emulator entirely: jest with @react-native/jest-preset + @testing-library/react-native renders the snippet's component tree in a pure node environment, which is enough to exercise the SDK's init+evaluate path against the live LaunchDarkly backend. Two adjustments versus a vanilla setup: 1. AppState.currentState is undefined in the jest env; the SDK's RNStateDetector translates that to ApplicationState.Background, and with automaticBackgroundHandling+runInBackground=false (the SDK defaults), the connection manager immediately switches to offline mode. jest.setup.js pins AppState.currentState = 'active'. 2. The SDK's streaming transport (RNEventSource, internal in @launchdarkly/react-native-client-sdk) is implemented with XMLHttpRequest, which doesn't exist in Node. The test mocks ReactNativeLDClient via a subclass that forces initialConnectionMode='polling' so the SDK uses fetch (built into Node 18+) instead. Per-validate stages App.tsx + src/welcome.tsx and runs jest. ~30s on a warm cache (mostly polling roundtrip + jest startup).
0d5c3a8 to
496c7ed
Compare
Validator runs on macos-latest with xcodebuild against an iPhone simulator. The Xcode project is generated at validate time via xcodegen from a yaml spec checked into the validator scaffold; the launchdarkly-ios-client-sdk dependency comes in via Swift Package Manager (avoiding the CocoaPods step the snippet shows the user — we're testing the snippet's runtime behavior, not the dependency manager). The harness drops the snippet's AppDelegate.swift + ViewController.swift into Sources/, runs xcodegen to materialize HelloIOS.xcodeproj, and runs `xcodebuild test`. The XCTest case manually wires a UILabel into the IBOutlet (no storyboard), drives viewDidLoad, observes the flag, and asserts the rendered text contains the canonical EXAM-HELLO line. Two snippet fixes also landed (fix-on-red against the now-running validator): - ViewController.swift: `(flagKey)` → `\(flagKey)` and `(result)` → `\(result)`. The original had broken Swift string interpolation, printing the literal token text. Carried verbatim from gonfalon. - Podfile fallback bumped from `'6.1.0'` (4 majors stale) to `'11.1.2'`. `mode: native` because xcodebuild + iOS Simulator can't run inside Linux containers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.